/*
 *   Copyright 2006 Wei Lian <lianwei2003.student@sina.com>
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation; either version 2
 *   of the License, or (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
/////////
#include <qapplication.h>
#include <qtabwidget.h>
#include <qvbox.h>
#include <qhbox.h>
#include <qlabel.h>
#include <qpushbutton.h>
#include <qstatusbar.h>
#include <qprocess.h>
#include <qlineedit.h>
#include <qsettings.h>
#include <qslider.h>
#include <qtimer.h>
#include <qtextedit.h>
/////////external functions


////class
class Feidian:public QTabWidget
{
  Q_OBJECT			////
public:
  Feidian (QWidget * parent = 0, const char *name = 0);

private:
  void forkfeidian ();
  void forkplayer (int);
  void startplayer ();
//  void killfeidian ();
  void killplayer ();
  int scan_port (int *, char *, int, int);
  int portcandidate;
  int sound_read ();
  int socket_read (int, char *, int, int);
  int sound_handle;
  QPushButton *buttonplayer;
  QSlider *slidersound;
  QProcess *feidian;
  QProcess *player;
  QLineEdit *editwine;
  QLineEdit *editfeidian;
  QLineEdit *editplayer;
  QLineEdit *editportbegin;
  QLineEdit *editportend;
  QTextEdit *textstream;
  QStatusBar *barstatus;
  QLabel *labelscanedport;

  private slots:		////
  void onbuttonlaunchclicked ();
  void onbuttonquitclicked ();
  void onbuttonplayerclicked (bool);
  void onbuttonsaveclicked ();
  void onplayerexit ();
  void parsestderr ();
  void sound_write (int);
  void timerDone ();
};

Feidian::Feidian (QWidget * parent, const char *name):
QTabWidget (parent, name)
{
  QVBox *tab = new QVBox (this);
  QSettings settings;

  QHBox *hbox = new QHBox (tab);
  QLabel *label = new QLabel ("Wine:", hbox);
  editwine =
    new QLineEdit (settings.readEntry ("/feidian/wine", "wine"), hbox);

  hbox = new QHBox (tab);
  label = new QLabel ("Feidian path:", hbox);
  editfeidian =
    new QLineEdit (settings.
		   readEntry ("/feidian/feidian",
			      "/home/winapp/feidian-green.exe"), hbox);

  hbox = new QHBox (tab);
  label = new QLabel ("Player:", hbox);
  editplayer =
    new QLineEdit (settings.
		   readEntry ("/feidian/player",
			      "mplayer -ontop -geometry 100%:100%"), hbox);

  hbox = new QHBox (tab);
  label = new QLabel ("Port rang:", hbox);
  editportbegin =
    new QLineEdit (settings.readEntry ("/feidian/portbegin", "30000"), hbox);
  label = new QLabel ("-", hbox);
  editportend =
    new QLineEdit (settings.readEntry ("/feidian/portend", "65000"), hbox);

  hbox = new QHBox (tab);
  label = new QLabel ("URL: http://127.0.0.1:", hbox);
  labelscanedport = new QLabel (hbox);
  label = new QLabel ("/live.asf", hbox);

  hbox = new QHBox (tab);
  QPushButton *buttonlaunch = new QPushButton ("Launch",
					       hbox);
  buttonplayer = new QPushButton ("Player", hbox);
  buttonplayer->setToggleButton (TRUE);

  if ((sound_handle = open ("/dev/mixer", O_RDWR, 0)) == -1)
    {
      perror ("/dev/mixer");
      return;
    }
  slidersound =
    new QSlider (0, 100, 10, sound_read (), QSlider::Horizontal, hbox);
  QPushButton *buttonsave = new QPushButton ("Save",
					     hbox);

  textstream = new QTextEdit (tab);
  textstream->setTextFormat (Qt::LogText);
  barstatus = new QStatusBar (tab);


  addTab (tab, "Feidian");

  ///signal and slot
  connect (slidersound, SIGNAL (valueChanged (int)),
	   SLOT (sound_write (int)));
  connect
    (buttonlaunch,
     SIGNAL (clicked ()), this, SLOT (onbuttonlaunchclicked ()));
  connect
    (buttonplayer,
     SIGNAL (toggled (bool)), this, SLOT (onbuttonplayerclicked (bool)));
  connect (buttonsave, SIGNAL (clicked ()), this,
	   SLOT (onbuttonsaveclicked ()));

  feidian = new QProcess (this);
  connect (feidian, SIGNAL (readyReadStderr ()), this, SLOT (parsestderr ()));

  player = new QProcess (this);
  connect (player, SIGNAL (processExited ()), this, SLOT (onplayerexit ()));

  QTimer *timer = new QTimer (this);
  connect (timer, SIGNAL (timeout ()), this, SLOT (timerDone ()));
  timer->start (3000, FALSE);

  portcandidate = 50000;
}

void
Feidian::timerDone ()
{
  ////sound volume
  int value = sound_read ();
  if (slidersound->value () != value)
    slidersound->setValue (value);
  ////player state
  if ((!player->isRunning ()) && buttonplayer->isOn ())
    startplayer ();
}

void
Feidian::onplayerexit ()
{
  buttonplayer->setPaletteForegroundColor (Qt::black);
}

void
Feidian::onbuttonlaunchclicked ()
{
//  killfeidian ();
  barstatus->message ("launching feidian TV...");
  forkfeidian ();
}

void
Feidian::onbuttonplayerclicked (bool b)
{

  if (b)
    ////toggled on
    {
      killplayer ();
      startplayer ();
    }
  else
    ////toggled off
    {
      buttonplayer->setPaletteForegroundColor (Qt::black);
      killplayer ();
    }
}

void
Feidian::onbuttonquitclicked ()
{
  killplayer ();
//  killfeidian ();
}

void
Feidian::onbuttonsaveclicked ()
{
  QSettings settings;
  settings.writeEntry ("/feidian/wine", editwine->text ());
  settings.writeEntry ("/feidian/feidian", editfeidian->text ());
  settings.writeEntry ("/feidian/player", editplayer->text ());
  settings.writeEntry ("/feidian/portbegin", editportbegin->text ());
  settings.writeEntry ("/feidian/portend", editportend->text ());
}

void
Feidian::forkfeidian ()
{
  feidian->clearArguments ();
  feidian->addArgument (editwine->text ());
  feidian->addArgument (editfeidian->text ());
  feidian->start ();
}

void
Feidian::startplayer ()
{
  int port, result;
  char strport[8];

  result = scan_port (&port, "127.0.0.1", portcandidate, portcandidate);
  if (result == 0)		///connected, but no data
    {
      portcandidate = port;
      sprintf (strport, "%d", port);
      labelscanedport->setText (strport);
      buttonplayer->setPaletteForegroundColor (Qt::red);
      return;
    }
  else if (result < 0)		///not connected
    {
      result =
	scan_port
	(&port, "127.0.0.1",
	 (editportbegin->text ()).toInt (), (editportend->text ()).toInt ());
      if (result == 0)
	{
	  portcandidate = port;
	  sprintf (strport, "%d", port);
	  labelscanedport->setText (strport);
	  buttonplayer->setPaletteForegroundColor (Qt::red);
	  return;
	}
      else if (result < 0)
	{
	  labelscanedport->setText (" ? ");
	  buttonplayer->setPaletteForegroundColor (Qt::black);
	  return;
	}
    }

  ////update portcandidate
  portcandidate = port;
  ////
  sprintf (strport, "%d", port);
  labelscanedport->setText (strport);
  forkplayer (port);
}

void
Feidian::forkplayer (int port)
{
  if (player->isRunning ())
    return;

  buttonplayer->setPaletteForegroundColor (Qt::blue);

  char strport[8];
  sprintf (strport, "%d", port);
  ////
  player->clearArguments ();
  QStringList strlst = QStringList::split (" ", editplayer->text ());
  for (QStringList::Iterator it = strlst.begin (); it != strlst.end (); ++it)
    {
      player->addArgument (*it);
    }

  char *url;
  url =
    (char *)
    malloc (strlen ("http://127.0.0.1:/live.asf") + strlen (strport) + 1);
  strcpy (url, "http://127.0.0.1:");
  strcat (url, strport);
  strcat (url, "/live.asf");
  player->addArgument (url);
  free (url);
  player->start ();
}

/*void
Feidian::killfeidian ()
{
  if (feidian->isRunning ())
    {
    }
}*/

void
Feidian::killplayer ()
{
  if (player->isRunning ())
    {
      player->tryTerminate ();
    }

}

void
Feidian::parsestderr ()
{
  QString str;
  str = QString (feidian->readStderr ());
//  printf ("%s", str.section ("@", 1).ascii ());
  textstream->append (str.section ("@", 2));
  if (str.contains ("@start player"))
    {
      barstatus->message (tr ("start player"));
      if (buttonplayer->isOn ())
	startplayer ();
    }
  if (str.contains ("@try to connect..."))
    barstatus->message ("try to connect...");
  if (str.contains ("@Resource Not Found!"))
    barstatus->message ("Resource Not Found!");
}

int
Feidian::sound_read (void)
{
  int result;
  if (::ioctl (sound_handle, MIXER_READ (4), &result) == -1)
    {
      perror ("read_error");
      return -1;
    }
  result = ((result >> 8) + (result & 0xFF)) >> 1;
  result = (result > 0) ? result : 0;
  result = (result < 100) ? result : 100;
  return result;
}

void
Feidian::sound_write (int leftright)
{
  leftright = (leftright < 0) ? 0 : leftright;
  leftright = (leftright > 100) ? 100 : leftright;
  leftright = (leftright << 8) | (leftright & 0xFF);
  if (::ioctl (sound_handle, MIXER_WRITE (4), &leftright) == -1)
    {
      perror ("write_error");
      return;
    }
}

int
Feidian::scan_port (int *outputport, char *host, int begin, int end)
	////-3:err open sock, -2:err no host, -1:err connect to server, 0:connected, but no data, 1:data
{
  int sock, portnum;
  struct sockaddr_in serv_addr;
  struct hostent *server;
  ////
  char str[400];
  int recvN;
  begin = (begin >= 0) ? begin : 0;
  begin = (begin <= 65535) ? begin : 65535;
  end = (end >= 0) ? end : 0;
  end = (end <= 65535) ? end : 65535;

  server =::gethostbyname (host);
  if (server == NULL)
    {
      perror ("ERROR, no such host");
      return -2;
    }
  ::memset ((char *) &serv_addr, 0, sizeof (serv_addr));
  serv_addr.sin_family = AF_INET;
  ::memcpy ((char *) &serv_addr.sin_addr.s_addr,
	    (char *) server->h_addr, server->h_length);

  for (portnum = begin; portnum <= end; portnum++)
    {
      sock =::socket (AF_INET, SOCK_STREAM, 0);
      if (sock == -1)
	{
	  perror ("ERROR opening socket");
	  return -3;
	}

      serv_addr.sin_port = htons (portnum);

      if (::connect
	  (sock, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) == 0)
	////found a possible server
	{
	  sprintf (str,
		   "GET /live.asf HTTP/1.0\r\n"
		   "Host: %s:%d\r\n"
		   "Accept: */*\r\n"
		   "Accept-Language: en-us\r\n"
		   "Accept-Encoding: gzip, deflate\r\n"
		   "User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n"
		   "Connection: Keep-Alive\r\n\r\n", host, portnum);
	  ////send to server
	  ::write (sock, str, strlen (str));
	  ////receive data
	  recvN = socket_read (sock, str, sizeof (str), 1);
//        printf ("recv num=%d\n", recvN);
	  if (recvN > 15)
	    if (strncmp (str, "HTTP/1.0 200 OK", strlen ("HTTP/1.0 200 OK"))
		== 0)
	      ////connected to server
	      {
		if (recvN == sizeof (str))
		  ////data availible
		  {
		    ::close (sock);
		    *outputport = portnum;
		    return 1;
		  }
		else
		  ////no data
		  {
		    ::close (sock);
		    *outputport = portnum;
		    return 0;
		  }
	      }
	}
      ::close (sock);
    }
  return -1;
}

int
Feidian::socket_read (int sock, char *buffer, int size, int timeout)
	////-2:timeout, -1:select err, -3:recv err, 0:server shutdown
{
  int ret, red = 0;
  fd_set fds;
  struct timeval tv;

  FD_ZERO (&fds);
  while (size)
    {
      if (timeout > 0)
	{
	  // Wait up to 'timeout' seconds for data on socket to be ready for read
	  FD_SET (sock, &fds);
	  tv.tv_sec = timeout;
	  tv.tv_usec = 0;
	  ret =::select (sock + 1, &fds, NULL, NULL, &tv);
	  if (ret == -1)
	    {
	      printf ("select failed\n");
	      return -1;
	    }
	  if (ret != 1)
	    {
	      return -2;
	    }
	}

      ret =::read (sock, buffer + red, size);
      if (ret == -1)
	{
	  printf ("RECV failed\n");
	  return -3;
	}
      /* Got zero bytes on blocking read.  For unix this is an 
         orderly shutdown. */
      if (ret == 0)
	{
//        printf ("recv recieved zero bytes!\n");
	  break;
	}
      red += ret;
      size -= ret;
    }
  return red;
}

int
main (int argc, char **argv)
{
  QApplication a (argc, argv);
  Feidian w;
  QObject::
    connect (&a, SIGNAL (aboutToQuit ()), &w, SLOT (onbuttonquitclicked ()));
  w.resize (350, 300);
  a.setMainWidget (&w);
  w.show ();
  return a.exec ();
}

#include "main.moc"
